using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

using System.Xml;
using System.Drawing.Drawing2D;
using System.IO;

namespace GDIdotNETgames
{
    public partial class CustomControlMap : Control
    {
        private Size _size = new Size();
        private ClassMapImage _image = null;
        private ClassCamera _camera = null;
        private Dictionary<string, ClassSprite> _sprites = new Dictionary<string, ClassSprite>();
        private ClassSprite _draggedSpriteTranslate = null;
        private ClassSprite _draggedSpriteRotate = null;
        private Point _dragOrigin = Point.Empty;
        private PointF _dragRotationScreenCenter = PointF.Empty;
        private PointF _dragRotationCenter = PointF.Empty;
        private float _dragRotationAngle = 0;

        public Point MaximumScroll
        {
            get { return new Point((int)(_size.Width - _camera.Viewport.Width), (int)(_size.Height - _camera.Viewport.Height)); }
        }
        public float MaximumZoom
        {
            get { return Math.Min(_size.Width / _camera.Screen.Width, _size.Height / _camera.Screen.Height); }
        }

        public delegate void MapAutoScrollEventHandler(object sender, MapAutoScrollEventArgs mase);
        public event MapAutoScrollEventHandler MapAutoScroll;
        protected virtual void OnMapAutoScroll(MapAutoScrollEventArgs mase)
        {
            MapAutoScrollEventHandler handler = MapAutoScroll;
            if (MapAutoScroll != null)
                handler(this, mase);
        }

        public CustomControlMap(Rectangle screen, XmlReader reader)
        {
            InitializeComponent();
            SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            DoubleBuffered = true; // how many of the above SetStyle()s are covered by this property?
            Anchor = ((AnchorStyles)(
                AnchorStyles.Top |
                AnchorStyles.Bottom |
                AnchorStyles.Left |
                AnchorStyles.Right));
            Location = screen.Location;
            Size = screen.Size;
            timerAnimation.Enabled = false;
            while (reader.Read())
            {
                if (reader.IsStartElement("Animation"))
                {
                    timerAnimation.Interval = 1000 / XmlConvert.ToInt32(reader.GetAttribute("FPS"));
                    timerAnimation.Enabled = true;
                }
                if (reader.IsStartElement("Map"))
                {
                    _size = new Size(
                        XmlConvert.ToInt32(reader.GetAttribute("Width")),
                        XmlConvert.ToInt32(reader.GetAttribute("Height")));
                    XmlReader mapReader = reader.ReadSubtree();
                    while (mapReader.Read())
                        if (mapReader.IsStartElement("Image"))
                            if (File.Exists(AppDomain.CurrentDomain.BaseDirectory + mapReader.GetAttribute("Filename")))
                                _image = new ClassMapImage(mapReader, _size);
                }
                if (reader.IsStartElement("Sprite"))
                {
                    ClassSprite sprite = new ClassSprite(reader);
                    _sprites.Add(sprite.Name, sprite);
                }
            }
            _camera = new ClassCamera(DisplayRectangle);
            Resize += new EventHandler(CustomControlMap_Resize);
            MouseMove += new MouseEventHandler(CustomControlMap_MouseMove);
            MouseDown += new MouseEventHandler(CustomControlMap_MouseDown);
            MouseUp += new MouseEventHandler(CustomControlMap_MouseUp);
        }

        public void SerializeToXML(XmlWriter writer)
        {
            if (timerAnimation.Enabled)
            {
                writer.WriteStartElement("Animation");
                int fps = 1000 / timerAnimation.Interval;
                writer.WriteAttributeString("FPS", fps.ToString());
                writer.WriteEndElement();
            }
            writer.WriteStartElement("Map");
            writer.WriteAttributeString("Width", _size.Width.ToString());
            writer.WriteAttributeString("Height", _size.Height.ToString());
            if (_image != null)
                _image.SerializeToXML(writer);
            writer.WriteEndElement();
            foreach (ClassSprite sprite in _sprites.Values)
                sprite.SerializeToXML(writer);
        }

        public void Scroll(PointF scrollTo)
        {
            if (_draggedSpriteTranslate != null)
            {
                _dragOrigin.X -= (int)(_camera.Scale.Width * (scrollTo.X - _camera.Location.X));
                _dragOrigin.Y -= (int)(_camera.Scale.Height * (scrollTo.Y - _camera.Location.Y));
            }
            _camera.Location = scrollTo;
            Invalidate();
        }

        public void Zoom(float zoomTo)
        {
            _camera.Size = new SizeF(_camera.Screen.Width * zoomTo, _camera.Screen.Height * zoomTo);
            Invalidate(); // during a spriteDrag need to update the _dragOrigin, but usable for now like it is
        }

        private bool IsOverSprite(Point point, ClassSprite sprite)
        {
            GraphicsPath path = sprite.Path;
            path.Transform(_camera.Transform);
            return path.IsVisible(point);
        }

        private bool IsOverSpriteTopLeft(Point point, ClassSprite sprite)
        {
            GraphicsPath path = sprite.TopLeftPath;
            path.Transform(_camera.Transform);
            return path.IsVisible(point);
        }

        private bool IsOverSpriteTopRight(Point point, ClassSprite sprite)
        {
            GraphicsPath path = sprite.TopRightPath;
            path.Transform(_camera.Transform);
            return path.IsVisible(point);
        }

        private PointF SpriteOutOfBoundsVector(RectangleF rectF, ClassSprite sprite)
        {
            RectangleF bounds = sprite.Path.GetBounds();
            PointF vector = PointF.Empty;
            if (bounds.Left < rectF.Left && bounds.Right <= rectF.Right)
            {
                vector.X = bounds.Left - rectF.Left;
            }
            if (bounds.Right > rectF.Right && bounds.Left >= rectF.Left)
            {
                vector.X = bounds.Right - rectF.Right;
            }
            if (bounds.Top < rectF.Top && bounds.Bottom <= rectF.Bottom)
            {
                vector.Y = bounds.Top - rectF.Top;
            }
            if (bounds.Bottom > rectF.Bottom && bounds.Top >= rectF.Top)
            {
                vector.Y = bounds.Bottom - rectF.Bottom;
            }
            return vector;
        }

        private bool IsSpriteBeyondMap(ClassSprite sprite)
        {
            PointF check = SpriteOutOfBoundsVector(new RectangleF(new Point(0, 0), _size), sprite);
            return check != PointF.Empty;
        }

        private bool IsSpriteCollision(ClassSprite sprite1, ClassSprite sprite2)
        {
            bool collision = false;
            foreach (PointF ptF in sprite2.Path.PathPoints)
                if (sprite1.Path.IsVisible(ptF))
                    collision = true;
            foreach (PointF ptF in sprite1.Path.PathPoints)
                if (sprite2.Path.IsVisible(ptF))
                    collision = true;
            return collision;
        }

        private bool IsSpriteOutOfRange(ClassSprite tstSprite)
        {
            bool outOfRange = IsSpriteBeyondMap(tstSprite);
            foreach (ClassSprite sprite in _sprites.Values)
                if (sprite != tstSprite)
                    outOfRange |= IsSpriteCollision(tstSprite, sprite);
            return outOfRange;
        }

        private Rectangle SpritePenBoundingScreenRectangle(ClassSprite sprite)
        {
            RectangleF rectF = new RectangleF();
            if (sprite.MouseOver)
                rectF = sprite.Path.GetBounds(_camera.Transform, Pens.Red);
            else
                rectF = sprite.Path.GetBounds(_camera.Transform, Pens.Black);
            rectF.Inflate(_camera.Scale);
            Rectangle rect = new Rectangle((int)rectF.X, (int)rectF.Y, (int)rectF.Width, (int)rectF.Height);
            rect.Inflate(1, 1);
            return rect;
        }

        void CustomControlMap_MouseMove(object sender, MouseEventArgs e)
        {
            if (_draggedSpriteTranslate == null && _draggedSpriteRotate == null)
            {
                foreach (ClassSprite sprite in _sprites.Values)
                {
                    if (sprite.MouseOver != IsOverSprite(e.Location, sprite))
                    {
                        sprite.MouseOver = IsOverSprite(e.Location, sprite);
                        Invalidate(SpritePenBoundingScreenRectangle(sprite));
                    }
                    if (sprite.MouseOver)
                    {
                        if (sprite.MouseOverTopLeft != IsOverSpriteTopLeft(e.Location, sprite))
                        {
                            sprite.MouseOverTopLeft = IsOverSpriteTopLeft(e.Location, sprite);
                            Invalidate(SpritePenBoundingScreenRectangle(sprite));
                        }
                        if (sprite.MouseOverTopRight != IsOverSpriteTopRight(e.Location, sprite))
                        {
                            sprite.MouseOverTopRight = IsOverSpriteTopRight(e.Location, sprite);
                            Invalidate(SpritePenBoundingScreenRectangle(sprite));
                        }
                    }
                }
            }
            if (_draggedSpriteTranslate != null)
            {
                PointF translateVector = new PointF(
                    (e.Location.X - _dragOrigin.X) / _camera.Scale.Width,
                    (e.Location.Y - _dragOrigin.Y) / _camera.Scale.Height); // desired vector offset from _dragStartLocation
                float magnitude = (float)Math.Sqrt(translateVector.X * translateVector.X + translateVector.Y * translateVector.Y);
                PointF normalizedVector = new PointF(translateVector.X / magnitude, translateVector.Y / magnitude);
                int steps = 0;
                while // could use Location in place of steps * normalizedVector BUT THAT WOULD NOT BE RELATIVE TO translateVector
                (
                    Math.Sqrt(
                        (steps * normalizedVector.X - translateVector.X) * (steps * normalizedVector.X - translateVector.X) +
                        (steps * normalizedVector.Y - translateVector.Y) * (steps * normalizedVector.Y - translateVector.Y))
                        > 1 &&
                    !IsSpriteOutOfRange(_draggedSpriteTranslate)
                )
                {
                    _draggedSpriteTranslate.Location = new PointF(
                        _draggedSpriteTranslate.Location.X + normalizedVector.X,
                        _draggedSpriteTranslate.Location.Y + normalizedVector.Y);
                    steps++;
                }
                if (IsSpriteOutOfRange(_draggedSpriteTranslate))
                {
                    _draggedSpriteTranslate.Location = new PointF(
                        _draggedSpriteTranslate.Location.X - normalizedVector.X,
                        _draggedSpriteTranslate.Location.Y - normalizedVector.Y);
                    steps--;
                }

                // SOMETHING STILL VERY WRONG WITH THE FOLLOWING
                _dragOrigin.X += (int)(steps * normalizedVector.X * _camera.Scale.Width);
                _dragOrigin.Y += (int)(steps * normalizedVector.Y * _camera.Scale.Height);

                Invalidate(); // really should only invalidate the sprite movement rectangle
            }
            if (_draggedSpriteRotate != null)
            {
                float rotationAngle = (float)(((180d / Math.PI) * Math.Atan2(
                    e.Location.Y - _dragRotationScreenCenter.Y,
                    e.Location.X - _dragRotationScreenCenter.X)));
                rotationAngle -= _dragRotationAngle; // desired angle
                float rotationDirection = Math.Sign(rotationAngle);
                if (Math.Abs(rotationAngle - _dragRotationAngle) > 180)
                {

                    // SOMETHING IS STILL VERY WRONG WITH THIS
                    rotationAngle -= 360 * rotationDirection;
                    rotationDirection = -rotationDirection;

                }
                int steps = 0;
                while // could use Rotation in place of steps * rotationDirection BUT THAT WOULD NOT BE RELATIVE TO rotationAngle
                (
                    Math.Abs(steps * rotationDirection - rotationAngle) > 1 &&
                    !IsSpriteOutOfRange(_draggedSpriteRotate)
                )
                {
                    _draggedSpriteRotate.Transform.RotateAt(rotationDirection, _dragRotationCenter, MatrixOrder.Append);
                    steps++;
                }
                if (IsSpriteOutOfRange(_draggedSpriteRotate))
                {
                    _draggedSpriteRotate.Transform.RotateAt(-rotationDirection, _dragRotationCenter, MatrixOrder.Append);
                    steps--;
                }

                // SOMETHING IS STILL VERY WRONG WITH THIS
                _dragRotationAngle += steps * rotationDirection;
                if (Math.Abs(_dragRotationAngle) > 180)
                    _dragRotationAngle -= 360 * rotationDirection;

                Invalidate(); // as above for invalidate after translate, but the bounding rect for the movement is figured differently
            }
        }

        void CustomControlMap_MouseDown(object sender, MouseEventArgs e)
        {
            foreach (ClassSprite sprite in _sprites.Values)
            {
                if (IsOverSprite(e.Location, sprite))
                {
                    _dragOrigin = e.Location;
                    if (IsOverSpriteTopLeft(e.Location, sprite))
                    {
                        _draggedSpriteRotate = sprite;
                        _dragRotationCenter = _draggedSpriteRotate.TopRight;
                    }
                    if (IsOverSpriteTopRight(e.Location, sprite))
                    {
                        _draggedSpriteRotate = sprite;
                        _dragRotationCenter = _draggedSpriteRotate.TopLeft;
                    }
                    if (_draggedSpriteRotate == null)
                        _draggedSpriteTranslate = sprite;
                    if (_draggedSpriteTranslate != null)
                    {
                        timerAutoScroll.Enabled = true;
                    }
                    if (_draggedSpriteRotate != null)
                    {
                        PointF[] rotCscreen = new PointF[] { _dragRotationCenter };
                        _camera.Transform.TransformPoints(rotCscreen);
                        _dragRotationScreenCenter = rotCscreen[0];
                        _dragRotationAngle = (float)(((180d / Math.PI) * Math.Atan2(
                            e.Location.Y - _dragRotationScreenCenter.Y,
                            e.Location.X - _dragRotationScreenCenter.X)));
                    }
                }
            }
        }

        void CustomControlMap_MouseUp(object sender, MouseEventArgs e)
        {
            _draggedSpriteTranslate = null;
            _draggedSpriteRotate = null;
            _dragOrigin = Point.Empty;
            _dragRotationScreenCenter = PointF.Empty;
            _dragRotationCenter = PointF.Empty;
            _dragRotationAngle = 0;
            timerAutoScroll.Enabled = false;
            Invalidate();
        }

        private void timerAutoScroll_Tick(object sender, EventArgs e)
        {
            if (_draggedSpriteTranslate != null)
            {
                PointF vector = SpriteOutOfBoundsVector(_camera.Viewport, _draggedSpriteTranslate);
                if (vector != PointF.Empty)
                {
                    _draggedSpriteTranslate.Transform.Translate(vector.X, vector.Y, MatrixOrder.Append);
                    OnMapAutoScroll(new MapAutoScrollEventArgs(new Point((int)vector.X, (int)vector.Y)));
                }
            }
        }

        private void timerAnimation_Tick(object sender, EventArgs e)
        {
            foreach (ClassSprite sprite in _sprites.Values)
            {
                sprite.Update();
                Invalidate(SpritePenBoundingScreenRectangle(sprite));
            }
        }

        void CustomControlMap_Resize(object sender, EventArgs e)
        {
            _camera.Screen = DisplayRectangle;
        }

        protected override void OnPaint(PaintEventArgs pe)
        {
            pe.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
            if (_image != null)
                pe.Graphics.DrawImage(_image.Image, _camera.Screen, _camera.Viewport, GraphicsUnit.Pixel);
            foreach (ClassSprite sprite in _sprites.Values)
                sprite.Draw(pe.Graphics, _camera.Transform.Clone());
        }
    }
}
